package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.gateway.properties.FilterProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author 钟涛
 * @version 1.0
 * @description: TODO
 * @date 2021/12/21 11:33
 */
@Component
public class RequestAuthFilter implements GlobalFilter {
    @Autowired
    private FilterProperties filterProperties;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private String innerPattern = "/**/inner/**";

    private AntPathMatcher pathMatcher = new AntPathMatcher();
    /**
     * 过滤器逻辑
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //当前的请求
        ServerHttpRequest request = exchange.getRequest();
        // //当前的响应
        ServerHttpResponse response = exchange.getResponse();
        //当前的请求路径
        String path = request.getURI().getPath();

        ServerHttpRequest.Builder reqBuilder = request.mutate();
        //feign声明的api。集群内的所有远程调用，不允许任何人通过网关发送请求尝试
        if(pathMatcher.match(innerPattern, path)){
            //请求路径为集群内的远程调用  ，返回403
            response.setStatusCode(HttpStatus.FORBIDDEN);
            Result fail = Result.fail();
            fail.setMessage("拒绝访问");
            String jsonResponse = "";
            try {
                jsonResponse = new ObjectMapper().writeValueAsString(fail);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
            DataBuffer wrap = dataBufferFactory.wrap(jsonResponse.getBytes(StandardCharsets.UTF_8));
            Mono<DataBuffer> just = Mono.just(wrap);
            response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
            Mono<Void> voidMono = response.writeWith(just);
            return voidMono;
        }


        boolean authStatus = false;
        //需要登录认证的请求集合
        List<String> auth = filterProperties.getAuth();
        for (String pattern : auth) {
            authStatus = pathMatcher.match(pattern, path);
            if(authStatus)break;
        }

        String token = getToken(request);
        if(!StringUtils.isEmpty(token)){
            String userId = getUserId(token,request);
            if(StringUtils.isEmpty(userId)){
                return redirectToLoginUrl(request,response);
            }else{
                reqBuilder.header("userId",userId);
            }
        }

        //需要登陆的路径
        if(authStatus){
            //当前请求需要进行认证
            if(StringUtils.isEmpty(token)){
                //需要登录的路径，缺不带token,重定向到登录页 302
                return redirectToLoginUrl(request, response);//响应结束
            }else{
                //有token
                String userId = getUserId(token,request);//对token进行验证，成功就返回用户id,失败就返回null
                if(StringUtils.isEmpty(userId)){
                    //userId不能根据token获取到,即token为假的
                    return redirectToLoginUrl(request,response);
                }else{
                    //userId能根据token获取到,即token为真的，网关要往下透传userId

                    //前端有可能会带userTempId
                    String userTempId = getUserTempId(request);
                    reqBuilder.header("userTempId",userTempId)
                              .header("userId", userId);

                }
            }

        }

        //前端有可能会带userTempId
        String userTempId = getUserTempId(request);
        if(!StringUtils.isEmpty(userTempId)){
            reqBuilder.header("userTempId",userTempId);
        }
        ServerHttpRequest newRequest = reqBuilder.build();
        ServerWebExchange webExchange = exchange.mutate().request(newRequest).response(response).build();

        /**
         * 程序走到这一步代表的情况为:
         *     不需要进行登陆的普通路径,直接进行放行
         *  直接放行存在的问题:
         *      无需登陆验证的请求，有可能请求字段存在假冒的userId：
         *         解决问题: 其实不用解决，因为带上userId的就是需要登陆验证的请求，不带的就是不需要的。
         */
        return chain.filter(webExchange);
    }

    private String getUserTempId(ServerHttpRequest request) {
        List<String> userTempIds = request.getHeaders().get("userTempId");
        if(!CollectionUtils.isEmpty(userTempIds)){
            //请求头中有userTempId
            return userTempIds.get(0);
        }
        //请求头中没有userTempId，继续看cookie中有没有
        List<HttpCookie> cookies = request.getCookies().get("userTempId");
        if(cookies!=null){
            //cookie中有
            return cookies.get(0).getValue();
        }
        return null;
    }

    private String getUserId(String token, ServerHttpRequest request) {
        String json = redisTemplate.opsForValue().get(RedisConst.USER_LOGIN_KEY_PREFIX + token);
        if(StringUtils.isEmpty(json)){
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        try {
            Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
            });
            String ip = map.get("ip").toString();
            //当前请求的ip
            String ipAddress = IpUtil.getGatwayIpAddress(request);
            if(!ipAddress.equals(ip)){
                //校验ip不对等
                return null;
            }else{
                return map.get("userId").toString();
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Mono<Void> redirectToLoginUrl(ServerHttpRequest request, ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FOUND);
        String locationUrl =  filterProperties.getLoginUrl()+"?originUrl="+ request.getURI().toString();
        response.getHeaders().add("Location",locationUrl);
        Mono<Void> voidMono = response.setComplete();
        return voidMono;
    }

    private String getToken(ServerHttpRequest request){
        List<String> tokens = request.getHeaders().get("token");
        if(!CollectionUtils.isEmpty(tokens)){
            //请求头中有token
            return tokens.get(0);
        }
        //请求头中没有token，继续看cookie中有没有
        List<HttpCookie> cookies = request.getCookies().get("token");
        if(cookies!=null){
            //cookie中有
            return cookies.get(0).getValue();
        }
        return null;
    }
}
